package singleLayerNeuralNetWorks;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

import util.ActivationFunction;
import util.GaussianDistribution;

public class Perceptrons {

	private int nIn; // dimensions of input data

	private double[] w;

	public Perceptrons(int nIn) {
		this.nIn = nIn;

		w = new double[nIn];
	}

	public int train(double[] x, int t, double learningRate) {
		int classified = 0;
		double c = 0.;
		// check whether the data is classified correctly
		for (int i = 0; i < nIn; i++) {
			c += w[i] * x[i] * t;
		}
		// apply gradient descent method if the data is wrongly classified
		if (c > 0) {
			classified = 1;
		} else {
			for (int i = 0; i < nIn; i++) {
				w[i] += learningRate * x[i] * t;
			}
		}
		return classified;
	}

	public int predict(double[] x) {
		double preActivation = 0.;
		for (int i = 0; i < nIn; i++) {
			preActivation += w[i] * x[i];
		}
		return ActivationFunction.step(preActivation);
	}

	public double getW(int index) {
		return w[index];
	}

	public static void main(String args[]) {

		final int train_N = 10000000; // number of training data
		final int test_N = 1000; 	// number of test data
		final int nIn = 10; 		// dimensions of input data
		double[][] train_X = new double[train_N][nIn]; // input data for training
		int[] train_T = new int[train_N]; // output data (label) for training
		double[][] test_X = new double[test_N][nIn]; // input data for test
		int[] test_T = new int[test_N]; // label of inputs
		int[] predicted_T = new int[test_N]; 	// output data predicted by the model
		final int epochs = 5000; 				// maximum training epochs
		final double learningRate = 1.0; 		// learning rate can be 1 in perceptron

		final Random rng = new Random(1234); // seed random
		GaussianDistribution[] gs = new GaussianDistribution[nIn];
		double delta = 4.0 / (nIn - 1.0);
		for (int i = 0; i < nIn; i++) {
			gs[i] = new GaussianDistribution(-2.0 + i * delta, 1.0, rng);
		}

		for (int i = 0; i < train_N / 2; i++) {
			train_T[i] = 1;
			for (int k = 0; k < nIn; k++) {
				train_X[i][k] = gs[k].random();
			}
		}
		for (int i = 0; i < test_N / 2; i++) {
			test_T[i] = 1;
			for (int k = 0; k < nIn; k++) {
				test_X[i][k] = gs[k].random();
			}
		}
		for (int i = train_N / 2; i < train_N; i++) {
			train_T[i] = -1;
			for (int k = nIn - 1; k >= 0; k--) {
				train_X[i][nIn - k - 1] = gs[k].random();
			}
		}

		for (int i = test_N / 2; i < test_N; i++) {
			test_T[i] = -1;
			for (int k = nIn - 1; k >= 0; k--) {
				test_X[i][nIn - k - 1] = gs[k].random() + Math.random() * 15;
			}
		}

		Perceptrons classifier = new Perceptrons(nIn);

		SimpleDateFormat myFmt=new SimpleDateFormat("HH:mm:ss.SSS");

		int epoch = 0;
		Date begin = new Date();
		int classified_ = 0;
		System.out.println("Begin training..." +myFmt.format(new Date()));
		while (true) {
			classified_ = 0;
			for (int i = 0; i < train_N; i++) {
				classified_ += classifier.train(train_X[i], train_T[i], learningRate);
			}
			if (classified_ == train_N)
				break; // when all data classified correctly
			epoch++;
			if (epoch > epochs)
				break;
		}
		long duration = new Date().getTime() - begin.getTime();

		System.out.println("Begin evaluating..." +myFmt.format(new Date()));

		for (int i = 0; i < test_N; i++) {
			predicted_T[i] = classifier.predict(test_X[i]);
		}

		int[][] confusionMatrix = new int[2][2];
		double accuracy = 0.;
		double precision = 0.;
		double recall = 0.;
		for (int i = 0; i < test_N; i++) {
			if (predicted_T[i] > 0) {
				if (test_T[i] > 0) {
					accuracy += 1;
					precision += 1;
					recall += 1;
					confusionMatrix[0][0] += 1;
				} else {
					confusionMatrix[1][0] += 1;
				}
			} else {
				if (test_T[i] > 0) {
					confusionMatrix[0][1] += 1;
				} else {
					accuracy += 1;
					confusionMatrix[1][1] += 1;
				}
			}
		}
		accuracy /= test_N;
		precision /= confusionMatrix[0][0] + confusionMatrix[1][0];
		recall /= confusionMatrix[0][0] + confusionMatrix[0][1];

		System.out.printf("Time consumed by training: %d ms\n", duration);
		System.out.printf("Epoch: %d\n", epoch);
		System.out.printf("Classified in last epoch out of %d: %d\n", train_N, classified_);
		System.out.println("Perceptrons model evaluation");
		System.out.println("----------------------------");
		System.out.printf("Accuracy: \t%.1f %%\n", accuracy * 100);
		System.out.printf("Precision: \t%.1f %%\n", precision * 100);
		System.out.printf("Recall: \t%.1f %%\n", recall * 100);

		System.out.println("\n----------------------------");
		System.out.println("Coefficients:");
		for (int i = 0; i < nIn; i++) {
			System.out.println("w[" + i + "]\t= " + classifier.getW(i));
		}
	}
}
